<?php
namespace UHU;
class moment
{
	protected $unix;
/*      Day      */
	protected $d; # Day of the month, 2 digits with leading zeros
	protected $D; # A textual representation of a day, three letters
	protected $j; # Day of the month without leading zeros
	protected $l; # A full textual representation of the day of the week
	protected $N; # ISO-8601 representation of the day of the week
	protected $S; # English ordinal suffix for the day of the month, 2 characters
	protected $w; # Numeric representation of the day of the week
	protected $z; # The day of the year
/*      Week    */
	protected $W; # ISO-8601 week number of year, weeks starting on Monday
/*       Month   */
	protected $F; # A full textual representation of a month, such as January or March
	protected $m; # Numeric representation of a month, with leading zeros
	protected $M; # A short textual representation of a month, three letters
	protected $n; # Numeric representation of a month, without leading zeros
	protected $t; # Number of days in the given month
/*      Year       */
	protected $L; # Whether it's a leap year
	protected $o; # ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead.
	protected $Y; # A full numeric representation of a year, 4 digits
	protected $y; # A two digit representation of a year
/*       Time      */
	protected $a; # Lowercase Ante meridiem and Post meridiem
	protected $A; # Uppercase Ante meridiem and Post meridiem
	protected $B; # Swatch Internet time
	protected $g; # 12-hour format of an hour without leading zeros
	protected $G; # 24-hour format of an hour without leading zeros
	protected $h; # 12-hour format of an hour with leading zeros
	protected $H; # 24-hour format of an hour with leading zeros
	protected $i; # Minutes with leading zeros
	protected $s; # Seconds, with leading zeros
	protected $u; # Microseconds (added in PHP 5.2.2)
/*       Timezone     */
	protected $e; # Timezone identifier (added in PHP 5.1.0)
	protected $I; # Whether or not the date is in daylight saving time
	protected $O; # Difference to Greenwich time (GMT) in hours
	protected $P; # Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3)
	protected $T; # Timezone abbreviation
	protected $Z; # Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive
/*       Full Date/Time  */
	protected $c; # ISO 8601 date (added in PHP 5)
	protected $r; # RFC 2822 formatted date
	protected $U; # Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
	
	protected static function valid_chars()
	{
		static $valid_chars = array(
			'd','D','j','l','N','S','w','z',
			'W',
			'F','m','M','n','t',
			'L','o','Y','y',
			'a','A','B','g','G','h','H','i','s','u',
			'e','I','O','P','T','Z',
			'c','r','U'
		);
		return $valid_chars;
	}
	public function __construct($moment=null)
	{
		if(is_null($moment))
		{
			$moment = time();
		}
		if(is_object($moment))
		{
			if(in_array('__toString',get_class_methods($moment)))
			{
				$moment = (string)$moment;
			}
			else
			{
				throw new \InvalidArgumentException('Objects must have the __toString method to be processed.',100);
			}
		}
		if(is_string($moment) && ctype_digit($moment))
		{
			$moment = (integer)$moment;
		}
		if(is_string($moment))
		{
			if(ctype_xdigit($moment))
			{
				throw new \RuntimeException('Cannot process hexadecimal strings.',100);
			}
			else if(ctype_space($moment))
			{
				throw new \RuntimeException('Cannot process whitespace strings.',100);
			}
			else if(ctype_punct($moment))
			{
				throw new \RuntimeException('Cannot process punctuation strings.',100);
			}
			else if(ctype_cntrl($moment))
			{
				throw new \RuntimeException('Cannot process control character strings.',100);
			}
			else if(strtotime($moment) === false)
			{
				throw new \RuntimeException('Could not convert string to unix timestamp.',100);
			}
			else
			{
				$this->unix = strtotime($moment);
			}
		}
		else if(is_numeric($moment))
		{
			$this->unix = $moment;
		}
	}
	public function unix()
	{
		return $this->unix;
	}
	protected static function date(moment $moment,$date)
	{
		if(is_string($date) === false)
		{
			throw new \InvalidArgumentException('Date format must be specified as string.',200);
		}
		else if($date === '')
		{
			throw new \InvalidArgumentException('Date format cannot be empty string.',200);
		}
		else if(strlen($date) !== 1)
		{
			throw new \InvalidArgumentException('Date format must be single character.',200);
		}
		else if(in_array($date,static::valid_chars()) === false)
		{
			throw new \InvalidArgumentException('Specified date format character is not supported.',200);
		}
		else
		{
			return date($date,$moment->unix());
		}
	}
	public function ISO_8601_short()
	{
		return $this->Y() . '-' . $this->m() . '-' . $this->d();
	}
	public function ical()
	{
		return $this->Y() . $this->m() . $this->d() . 'T' . $this->H() . $this->i() . $this->s();
	}
	public function His()
	{
		return $this->H() . ':' . $this->i() . ':' . $this->s();
	}
	public function __call($name,$arguments)
	{
		if(strlen($name) > 1)
		{
			$chars = str_split($name);
			$doc = '';
			foreach($chars as $char)
			{
				$doc .= $this->$char();
			}
			return $doc;
		}
		if(isset($this->$name) === false)
		{
			if(in_array($name,self::valid_chars()) === false)
			{
				throw new \InvalidArgumentException('Specified date format character is not supported.',200);
			}
			$this->$name = self::date($this,$name);
		}
		return $this->$name;
	}
}
class rfc_moment extends moment
{
	public function __toString()
	{
		return $this->r();
	}
}
class iso_moment extends moment
{
	public function __toString()
	{
		return $this->c();
	}
}
interface Calendar_Event_metadata_funcs
{
	public function uid();
}
class Calendar_Event_metadata implements Calendar_Event_metadata_funcs
{
	protected $uid;
	public function __construct($uid=null)
	{
		if(is_null($uid))
		{
			$uid = NULL_KEY;
		}
		$this->uid = $uid;
	}
	public function uid()
	{
		return $this->uid;
	}
}
class Calendar_Event
{
	protected $dtstart;
	protected $dtend;
	protected $metadata;
	public function __construct(moment $dtstart,moment $dtend=null,Calendar_Event_metadata $metadata=null)
	{
		if(is_null($dtend))
		{
			$class = get_class($dtstart);
			$dtend = new $class((integer)$dtstart->U() + 1);
		}
		if(is_null($metadata))
		{
			$metadata = new Calendar_Event_metadata;
		}
		$this->dtstart  = $dtstart;
		$this->dtend    = $dtend;
		$this->metadata = $metadata;
	}
	public function dtstart()
	{
		return $this->dtstart;
	}
	public function dtend()
	{
		return $this->dtend;
	}
	public function duration()
	{
		static $duration;
		if(isset($duration) === false)
		{
			$duration = ((integer)$this->dtend()->U() - (integer)$this->dtstart()->U()) - 1;
		}
		return $duration;
	}
	public function uid()
	{
		return $this->metadata()->uid();
	}
	public function metadata()
	{
		return $this->metadata;
	}
}
class Calendar implements \Iterator,\Countable,\ArrayAccess
{
	protected $Events = array();
	public function count()
	{
		return count($this->Events);
	}
	public function current()
	{
		return current($this->Events);
	}
	public function key()
	{
		return key($this->Events);
	}
	public function next()
	{
		return next($this->Events);
	}
	public function rewind()
	{
		return reset($this->Events);
	}
	public function valid()
	{
		return (bool)$this->current();
	}
	public function offsetSet($offset,$value)
	{
		if(is_null($offset))
		{
			$offset = count($this);
		}
		if($value instanceof Calendar_Event)
		{
			if(in_array($value,$this->Events) === false)
			{
				$this->Events[$offset] = $value;
			}
			else
			{
				throw new RuntimeException('An attempt was made to add an event already present in the Calendar object.',400);
			}
		}
		else
		{
			throw new InvalidArgumentException('Values must be an instance of uhu_Calendar_Event.',400);
		}
	}
	public function offsetExists($offset)
	{
		return isset($this->Events[$offset]);
	}
	public function offsetUnset($offset)
	{
		unset($this->Events[$offset]);
	}
	public function offsetGet($offset)
	{
		return isset($this->Events[$offset]) ? $this->Events[$offset] : null;
	}
	public static function uasort(Calendar_Event $a,Calendar_Event $b)
	{
		if($a->dtstart()->U() === $b->dtstart()->U())
		{
			return strcmp($a->uid(),$b->uid());
		}
		else
		{
			return ($a->dtstart()->U() < $b->dtstart()->U()) ? -1 : 1;
		}
	}
	public function sort()
	{
		return uasort($this->Events,'\UHU\Calendar::uasort');
	}
}
class Calendar_restricted_by_month extends Calendar
{
	protected $month;
	protected $year;
	public function __construct(Calendar_Event $Event)
	{
		$this->year = $Event->dtstart()->Y();
		$this->month = $Event->dtstart()->m();
	}
	public function offsetSet($offset,$value)
	{
		if($value instanceof Calendar_Event)
		{
			if($value->dtstart()->m() !== $this->month || $value->dtstart()->Y() !== $this->year)
			{
				throw new InvalidArgumentException('That event does not match the previously defined year/month for this Calendar object (' .$this->year . ' ' . $this->month . ')',500);
			}
		}
		parent::offsetSet($offset,$value);
	}
	public function __toString()
	{
		$this->sort();
		foreach($this as $first)
		{
			break;
		}
		$first_week = (integer)$first->dtstart()->W();
		$doc =
			'<div class="uhu-calendar">' . "\n" .
			"\t" . '<h1 class="header"><abbr class="month-of-year" title="' . $first->dtstart()->F() . '">' . $first->dtstart()->M() . '</abbr> <span class="year">' . $first->dtstart()->Y() . '</span></h1>' . "\n" .
			"\t" . '<h2 class="days-of-week">' . "\n" .
			"\t\t" . '<abbr class="monday day-of-week" title="Monday">M</abbr>' . "\n" .
			"\t\t" . '<abbr class="tuesday day-of-week" title="Tuesday">T</abbr>' . "\n" .
			"\t\t" . '<abbr class="wednesday day-of-week" title="Wednesday">W</abbr>' . "\n" .
			"\t\t" . '<abbr class="thursday day-of-week" title="Thursday">T</abbr>' . "\n" .
			"\t\t" . '<abbr class="friday day-of-week" title="Friday">F</abbr>' . "\n" .
			"\t\t" . '<abbr class="saturday day-of-week" title="Saturday">S</abbr>' . "\n" .
			"\t\t" . '<abbr class="sunday day-of-week" title="Sunday">S</abbr>' . "\n" .
			"\t" . '</h2>' . "\n" .
			"\t" . '<ol class="days-of-month">' . "\n";
	foreach($this as $event)
	{
		$this_week = (integer)$event->dtstart()->W();
		$doc .=
			"\t\t\t" . '<li class="' . strtolower($event->dtstart()->l()) . ' week-' . (string)(($this_week - $first_week) + 1) . ' day-of-week"><span class="meta">' . $event->dtstart()->l() . ' </span>' . $event->dtstart()->j() . '<span class="meta">' . $event->dtstart()->S() . ' ' . $event->dtstart()->Y() . '</span></li>' . "\n";
	}
		$doc .=
			"\t" . '</ol>' . "\n" .
			'</div>';
		return $doc;
	}
}
?>